/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.persistence.dao;

import java.util.HashMap;

import org.telosys.common.SymbolicConfigVar;
import org.telosys.common.TelosysRuntimeException;
import org.telosys.persistence.mapping.EntityIntrospector;
import org.telosys.persistence.mapping.EntityMapping;
import org.telosys.util.ClassUtil;

/**
 * DAO and Mapper provider implementation
 *  
 * @author Laurent Guerin
 *
 */
public class ProviderImpl implements Provider
{
    private final HashMap<String,EntityDAO<?>>      _hmEntityDAO     = new HashMap<String,EntityDAO<?>>(128);
    private final HashMap<String,EntityMapping<?>>  _hmEntityMapping = new HashMap<String,EntityMapping<?>>(128);
    
    private final String _sMappingClassPattern ;
    
    /**
     * Constructs a new DAO provider 
     * 
     * @param mappingClassPattern the mapping class pattern ( like "org.demo.vo.daomapping.${BEANNAME}Mapping" )
     */
    public ProviderImpl(String mappingClassPattern)
    {
//        if ( mappingClassPattern != null )
//        {
//            _sMappingClassPattern = mappingClassPattern.trim() ;
//            if ( _sMappingClassPattern.length() == 0 )
//            {
//    	        throw new TelosysRuntimeException("Cannot create a provider with void pattern");
//            }
//        }
//        else
//        {
//	        throw new TelosysRuntimeException("Cannot create a provider with null pattern");
//        }
    	
    	//--- The mapping pattern can be null ( if annotations in bean classes )
        if ( mappingClassPattern != null ) {
        	String mappingPattern = mappingClassPattern.trim() ;
            if ( mappingPattern.length() > 0 ) {
            	//--- Not null and not void => keep it
            	_sMappingClassPattern = mappingPattern ;
            }
            else {
            	_sMappingClassPattern = null ;
            }
        }
        else {
        	_sMappingClassPattern = null ;
        }
    }        
    

	// @Override
	public <T> EntityDAO<T> getDAO(Class<T> entityClass) 
	{
		if ( null == entityClass ) throw new TelosysRuntimeException("Cannot get DAO, class argument is null");
		
		String entityClassName = entityClass.getName();
		EntityDAO<?> dao = _hmEntityDAO.get(entityClassName) ;
		if ( dao != null ) 
		{
			//--- Already instantiate => reuse it
			return (EntityDAO<T>) dao ;
		}
		else 
		{
			//--- Unknown => try to found the entity mapping definition and create the DAO
			EntityMapping<T> mapping = getEntityMapping(entityClass);
			if ( mapping != null ) 
			{
				//--- Mapping found => DAO creation
				dao = new EntityDAO<T> ( mapping ) ;
				_hmEntityDAO.put(entityClassName, dao) ;
				return (EntityDAO<T>) dao ;
			}
			else 
			{
				throw new TelosysRuntimeException("Cannot get mapping for entity class " + entityClassName + " (no mapping)");
			}
		}
	}
	
	// @Override
	public <T> EntityMapping<T> getEntityMapping(Class<T> entityClass) 
	{
		if ( null == entityClass ) throw new TelosysRuntimeException("Cannot get mapping, class argument is null");

		String entityClassName = entityClass.getName() ;
		
		//--- 1) Already loaded ?
		EntityMapping<T> mapping = ( EntityMapping<T> ) _hmEntityMapping.get( entityClassName ) ;
		if ( mapping != null ) 
		{
			return mapping ;
		}		

		//--- 2) Try to use annotations to define the mapping
		mapping = EntityIntrospector.getEntityMapping(entityClass);
		if ( mapping != null ) 
		{
			return mapping ;
		}		
		
		//--- 3) Try to load an existing mapping class by convention if convention is defined
		if ( _sMappingClassPattern != null ) {
			mapping = loadMappingDefinition(entityClass); 
			if ( mapping != null ) {
				//--- Found 
				_hmEntityMapping.put(entityClassName, mapping);
				return mapping ;
			}
		}
		
		//--- Definitively not found :-( 
		return null ;
		//throw new TelosysRuntimeException("Cannot get mapping for entity class " + entityClassName );
	}

	public <T> EntityMapping<T> loadMappingDefinition(Class<T> entityClass) 
	{
        //--- Build the DAO class name dynamically
        String sBeanName = ClassUtil.getSimpleName(entityClass); // "Author", "Book", "Employee", ...

        // String sDAOClass = Tool.buildClassName(_sDAOClassPattern, sBeanClass);
        String mappingClassName = SymbolicConfigVar.applyBeanName(_sMappingClassPattern, sBeanName); // v 1.1.0
	    
        Class<?> mappingClass = ClassUtil.tryToLoadClass(mappingClassName);            
        if ( mappingClass != null )
        {
        	//--- Instantiate the mapping
        	Object o = null ;
        	try {
    			o = mappingClass.newInstance();
    		} catch (InstantiationException e) {
    			throw new TelosysRuntimeException("Cannot create instance of " + mappingClassName + " (InstantiationException)", e);
    		} catch (IllegalAccessException e) {
    			throw new TelosysRuntimeException("Cannot create instance of " + mappingClassName + " (IllegalAccessException)", e);
    		}

        	if ( ( o instanceof EntityMapping ) != true ) {
    			throw new TelosysRuntimeException("Class " + mappingClassName + " is not a mapping class");
        	}
        	
        	EntityMapping<T> mappingInstance = (EntityMapping<T>) o;
        	Class<?> mappedClass = mappingInstance.getEntityClass() ;
        	if ( null == mappedClass ) {
    			throw new TelosysRuntimeException("Invalid mapping class " + mappingClassName + " (entity class is null)");
        	}
        	
        	String s1 = entityClass.getCanonicalName() ;
        	String s2 = mappedClass.getCanonicalName() ;
        	if ( ! s1.equals(s2) ) {
    			throw new TelosysRuntimeException("Invalid mapping class '" + mappingClassName 
    					+ "' (entity class is '" + s2 + "', '" + s1 + "' expected)");
        	}
        	
        	return mappingInstance ;
        }
        else
        {
            return null ; // Not found
        }
	}
		
}
